package at.fhooe.stefanirndorfer.dynamicchange;


import java.util.ArrayList;

import at.fhooe.stefanirndorfer.entity.AffectedAreaBoundaries;
import at.fhooe.stefanirndorfer.event.EventManager;
import at.fhooe.stefanirndorfer.event.GeometryChangedEvent;
import at.fhooe.stefanirndorfer.game.Game;
import at.fhooe.stefanirndorfer.labeling.Labeling_Manager;
import at.fhooe.stefanirndorfer.logging.Log;
import at.fhooe.stefanirndorfer.quadtree.QuadTreeNode;
import at.fhooe.stefanirndorfer.quadtree.QuadTree_Manager;
import at.fhooe.stefanirndorfer.walls.Wall;

import static at.fhooe.stefanirndorfer.utilities.Utilities.E;
import static at.fhooe.stefanirndorfer.utilities.Utilities.N;
import static at.fhooe.stefanirndorfer.utilities.Utilities.NE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.NW;
import static at.fhooe.stefanirndorfer.utilities.Utilities.S;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SW;
import static at.fhooe.stefanirndorfer.utilities.Utilities.W;


public class DynamicChangeHandler implements  GeometryChangedEvent.Listener {
	
	Labeling_Manager labeler;
	QuadTree_Manager quadtree_Manager;
	
	AffectedAreaBoundaries aab;
	
	public DynamicChangeHandler(Labeling_Manager labeler, QuadTree_Manager quadtree_Manager, EventManager event_manager){
		aab = null;
		
		this.labeler = labeler;
		this.quadtree_Manager = quadtree_Manager;
		
		event_manager.addListener(this, GeometryChangedEvent.class);
	}



	@Override
	public void onGeometryChangedEvent(GeometryChangedEvent geometryChangedEvent) {
		Log.info(this, "Geometry Changed Event caught!");
		
		ArrayList<Wall> affectedWalls = geometryChangedEvent.getWalls();
		double[] affectedAreaBounds = computeOuterBounds(affectedWalls);
		
		
		
		// find the lowest node, which totally includes the affected areas bounds
		QuadTreeNode n = findAllEmbracingNode(affectedAreaBounds, quadtree_Manager.getRootNode());
		
		deleteAllChildNodes(n);
		
		//recalculate Quadtree
		quadtree_Manager.recalculateQuadtree(n);
		
		//relabel
		labeler.relabel(n);
		//labeler.cleanUp_Merges();

		initAffectedAreaBoundaries(affectedAreaBounds);
		EmbracingNode embracingNode = new EmbracingNode(n.getWidth(), n.getHeight(),n.getPosX(), n.getPosY());
	}

	
	
	private void deleteAllChildNodes(QuadTreeNode n) {
		if(n.getChildNodes()[NW] != null){
			deleteAllChildNodes(n.getChildNodes()[NW]);
			deleteAllChildNodes(n.getChildNodes()[NE]);
			deleteAllChildNodes(n.getChildNodes()[SW]);
			deleteAllChildNodes(n.getChildNodes()[SE]);
		}else{
			if(n.destroy());
		}
	}



	/**
	 * returns the Node which totally includes the affected Area
	 * @param affectedAreaBounds
	 * @return
	 */
	private QuadTreeNode findAllEmbracingNode(double[] affectedAreaBounds,
			QuadTreeNode n) {
		QuadTreeNode r = n;
		QuadTreeNode[] childNodes = n.getChildNodes();
		
		if (totallyIncluded(childNodes[NW], affectedAreaBounds)) {
			r = findAllEmbracingNode(affectedAreaBounds, childNodes[NW]);
		} else {
			if (totallyIncluded(childNodes[NE], affectedAreaBounds)) {
				r = findAllEmbracingNode(affectedAreaBounds, childNodes[NE]);
			} else {
				if (totallyIncluded(childNodes[SW], affectedAreaBounds)) {
					r = findAllEmbracingNode(affectedAreaBounds, childNodes[SW]);
				} else {
					if (totallyIncluded(childNodes[SE], affectedAreaBounds)) {
						r = findAllEmbracingNode(affectedAreaBounds,
								childNodes[SE]);
					}
				}
			}
		}
		return r;
	}


	private boolean totallyIncluded(QuadTreeNode quadTreeNode,
			double[] affectedAreaBounds) {
		if(quadTreeNode.getPosX() < affectedAreaBounds[0] && quadTreeNode.getPosX()+ quadTreeNode.getWidth() > affectedAreaBounds[2]
				&& quadTreeNode.getPosY() > affectedAreaBounds[1] && quadTreeNode.getPosY()-quadTreeNode.getHeight() < affectedAreaBounds[3]){
			return true;
		}else{			
			return false;
		}
	}



	private void initAffectedAreaBoundaries(double[] affectedAreaBounds) {
		double width = affectedAreaBounds[2] - affectedAreaBounds[0];
		double height = affectedAreaBounds[1] - affectedAreaBounds[3];
		aab = new AffectedAreaBoundaries(width, height, affectedAreaBounds[0], affectedAreaBounds[1]);
	}

	
	/**
	 * 
	 * @param affectedWalls
	 * @returns the outer Boundaries of all affected Walls
	 */
	private double[] computeOuterBounds(ArrayList<Wall> affectedWalls) {
		
		double[] upperLeft = new double[]{Double.MAX_VALUE, -Double.MAX_VALUE}; 
		double[] lowerRight = new double[]{-Double.MAX_VALUE, Double.MAX_VALUE}; 
		
		for(Wall w : affectedWalls){
			
			if(w.getPosX() < upperLeft[0])
				upperLeft[0] = w.getPosX();
			
			if(w.getPosX() + w.getWidth() > lowerRight[0])
				lowerRight[0] = w.getPosX() + w.getWidth();
			
			if(w.getPosY() > upperLeft[1])
				upperLeft[1] = w.getPosY();
			
			if(w.getPosY() - w.getHeight() < lowerRight[1])
				lowerRight[1] = w.getPosY() - w.getHeight();
		}
		
		return new double[]{upperLeft[0], upperLeft[1], lowerRight[0], lowerRight[1]};
	}
	
}
